#include "ELFReader.h"

#include <QtAlgorithms>
#include <QFile>
#include <QDataStream>

#include <QDebug>

#include "libelf.h"
#include "gelf.h"

///////////////////////////
// ELFSymbol implementation
///////////////////////////

ELFSymbol::ELFSymbol()
: name("Undefined"), pointer(0)
{
}

ELFSymbol::ELFSymbol(unsigned long pointer)
: name("Undefined"), pointer(pointer)
{
}

ELFSymbol::ELFSymbol(const QString & name, unsigned long pointer)
: name(name), pointer(pointer)
{
}

bool ELFSymbol::operator< (const ELFSymbol & rh) const
{
    return (pointer < rh.pointer);
}

bool ELFSymbol::operator== (const ELFSymbol & rh) const
{
    return (pointer == rh.pointer);
}


///////////////////////////
// ELFReader implementation
///////////////////////////

void ELFReader::load(const QString & path)
{
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly))
        return;
    
    // clear all previous symbols
    symbols.clear();
    
    // read the file in memory
    long size = file.size();
    char *base_ptr = (char *)malloc(size);
    QDataStream in(&file);
    long read = 0;
    while (read < size)
        read += in.readRawData(base_ptr + read, size);
    
    // read all symbols
    Elf32_Ehdr *elf_header = 0;	// ELF header
    Elf *elf = 0;               // Our Elf pointer for libelf
    Elf_Scn *scn = 0;           // Section Descriptor
    Elf_Data *edata = 0;        // Data Descriptor
    GElf_Sym sym;               // Symbol
    GElf_Shdr shdr;             // Section Header
    
    if (elf_version(EV_CURRENT) == EV_NONE)
        qDebug() << "WARNING ELF Library is out of date";
    
    elf_header = (Elf32_Ehdr *)base_ptr;
    elf = elf_memory(base_ptr, size);
    
    while ((scn = elf_nextscn(elf, scn)) != NULL) {
        gelf_getshdr(scn, &shdr);
        
        // look for symbol tables
        if (shdr.sh_type == SHT_SYMTAB) {
            edata = elf_getdata(scn, edata);
            
            // derive the symbol count and get all symbols
            int symbol_count = shdr.sh_size / shdr.sh_entsize;
            for (int symbol = 0; symbol < symbol_count; symbol++) {
                gelf_getsym(edata, symbol, &sym);
                
                // skip FILE symbols
                int type = ELF32_ST_TYPE(sym.st_info);
                unsigned long pointer = sym.st_value;
                if (pointer != 0 || type == STT_OBJECT || type == STT_FUNC) {
                    QString name(elf_strptr(elf, shdr.sh_link, sym.st_name));
                    symbols.append(ELFSymbol(name, pointer));
                }
            }
        }
    }
    
    file.close();
    free(base_ptr);
    
    // append a base symbol to avoid invalid nearest symbol search results
    symbols.append(ELFSymbol("base", 0));
    
    qSort(symbols);
}

unsigned int ELFReader::size() const
{
    return symbols.size();
}

bool ELFReader::contains(unsigned long pointer) const
{
    if (size() == 0)
        return false;
    
    return (getNearestSymbol(pointer).pointer == pointer);
}

const ELFSymbol & ELFReader::getNearestSymbol(unsigned long pointer) const
{
    QVector<ELFSymbol>::const_iterator iter = qLowerBound(symbols, ELFSymbol(pointer));
    if (iter->pointer > pointer)
        iter--;
    return *iter;
}

QString ELFReader::operator[] (const unsigned long pointer) const
{
    if (size() == 0)
        return "Undefined";
    
    ELFSymbol nearest = getNearestSymbol(pointer);
    if (nearest.pointer == pointer)
        return nearest.name;
    else
        return QString("%1 + %2").arg(nearest.name).arg(pointer - nearest.pointer);
}

unsigned long ELFReader::address(const QString& symbol) const
{
    if (size() == 0)
        return 0;
    
    for (int i = 0; i < symbols.size(); ++i) {
        if (symbols.at(i).name == symbol)
            return symbols.at(i).pointer;
    }
    
    return 0;
}
